#include <cassert>

#include "nodebuilder.h"
#include "yaml-cpp/node/detail/node.h"
#include "yaml-cpp/node/impl.h"
#include "yaml-cpp/node/node.h"
#include "yaml-cpp/node/type.h"

namespace YAML {
struct Mark;

NodeBuilder::NodeBuilder()
    : m_pMemory(new detail::memory_holder),
      m_pRoot(nullptr), m_stack{}, m_anchors{}, m_keys{}, m_mapDepth(0) {
    m_anchors.push_back(nullptr); // since the anchors start at 1
}

NodeBuilder::~NodeBuilder() = default;

Node NodeBuilder::Root() {
    if (!m_pRoot)
        return Node();

    return Node(*m_pRoot, m_pMemory);
}

void NodeBuilder::OnDocumentStart(const Mark&) {}

void NodeBuilder::OnDocumentEnd() {}

void NodeBuilder::OnNull(const Mark& mark, anchor_t anchor) {
    detail::node& node = Push(mark, anchor);
    node.set_null();
    Pop();
}

void NodeBuilder::OnAlias(const Mark& /* mark */, anchor_t anchor) {
    detail::node& node = *m_anchors[anchor];
    Push(node);
    Pop();
}

void NodeBuilder::OnScalar(const Mark& mark, const std::string& tag,
                           anchor_t anchor, const std::string& value) {
    detail::node& node = Push(mark, anchor);
    node.set_scalar(value);
    node.set_tag(tag);
    Pop();
}

void NodeBuilder::OnSequenceStart(const Mark& mark, const std::string& tag,
                                  anchor_t anchor, EmitterStyle::value style) {
    detail::node& node = Push(mark, anchor);
    node.set_tag(tag);
    node.set_type(NodeType::Sequence);
    node.set_style(style);
}

void NodeBuilder::OnSequenceEnd() { Pop(); }

void NodeBuilder::OnMapStart(const Mark& mark, const std::string& tag,
                             anchor_t anchor, EmitterStyle::value style) {
    detail::node& node = Push(mark, anchor);
    node.set_type(NodeType::Map);
    node.set_tag(tag);
    node.set_style(style);
    m_mapDepth++;
}

void NodeBuilder::OnMapEnd() {
    assert(m_mapDepth > 0);
    m_mapDepth--;
    Pop();
}

detail::node& NodeBuilder::Push(const Mark& mark, anchor_t anchor) {
    detail::node& node = m_pMemory->create_node();
    node.set_mark(mark);
    RegisterAnchor(anchor, node);
    Push(node);
    return node;
}

void NodeBuilder::Push(detail::node& node) {
    const bool needsKey =
        (!m_stack.empty() && m_stack.back()->type() == NodeType::Map &&
         m_keys.size() < m_mapDepth);

    m_stack.push_back(&node);
    if (needsKey)
        m_keys.emplace_back(&node, false);
}

void NodeBuilder::Pop() {
    assert(!m_stack.empty());
    if (m_stack.size() == 1) {
        m_pRoot = m_stack[0];
        m_stack.pop_back();
        return;
    }

    detail::node& node = *m_stack.back();
    m_stack.pop_back();

    detail::node& collection = *m_stack.back();

    if (collection.type() == NodeType::Sequence) {
        collection.push_back(node, m_pMemory);
    }
    else if (collection.type() == NodeType::Map) {
        assert(!m_keys.empty());
        PushedKey& key = m_keys.back();
        if (key.second) {
            collection.insert(*key.first, node, m_pMemory);
            m_keys.pop_back();
        }
        else {
            key.second = true;
        }
    }
    else {
        assert(false);
        m_stack.clear();
    }
}

void NodeBuilder::RegisterAnchor(anchor_t anchor, detail::node& node) {
    if (anchor) {
        assert(anchor == m_anchors.size());
        m_anchors.push_back(&node);
    }
}
} // namespace YAML
